package com.atguigu.gmall.item.service.impl;

import com.atguigu.cache.annotate.GmallCache;
import com.atguigu.cache.service.BitmapService;
import com.atguigu.cache.service.CacheService;
import com.atguigu.gmall.common.constant.RedisConstant;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.JSONs;
import com.atguigu.gmall.feign.product.CategoryFeignClient;
import com.atguigu.gmall.feign.product.SkuInfoFeginClient;
import com.atguigu.gmall.item.service.SkuDetailService;
import com.atguigu.gmall.model.product.entity.SkuInfo;
import com.atguigu.gmall.model.product.entity.SpuSaleAttr;
import com.atguigu.gmall.model.product.to.CategoryView;
import com.atguigu.gmall.model.product.to.SkuDetailTO;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class SkuDetailServiceImpl implements SkuDetailService {
    @Autowired
    CategoryFeignClient categoryFeignClient;

    @Autowired
    SkuInfoFeginClient skuInfoFeginClient;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    CacheService cacheService;

    @Autowired
    BitmapService bitmapService;

    @Autowired
    ThreadPoolExecutor threadPoolExecutor;


    @Override
    @GmallCache(key = RedisConstant.REDIS_SKU_INFO_KEY + "#{#skuId}",
            bitmapKey = RedisConstant.REDIS_SKU_INFO_BITMAP,
            sleepTime = RedisConstant.REDIS_SKU_SLEEP_TIME,
            skuTTL = RedisConstant.REDIS_SKU_INFO_TTL
    )
    public SkuDetailTO getSkuDetail(Long skuId) {
        return getSkuDetailTOWithSource(skuId);
    }

    public SkuDetailTO getSkuDetailWithRedisson(Long skuId) {

        String cacheKey = RedisConstant.REDIS_SKU_INFO_KEY + skuId;
        //1. 缓存中拿到数据
        SkuDetailTO cacheData = cacheService.get(cacheKey, SkuDetailTO.class);
        if (cacheData != null) {
            log.info("缓存命中...");
            //如果缓存有的化就直接返回
            return cacheData;
        }
        //缓存没有的化，先查看位图里面有没有id，【避免恶意缓存穿透】
        boolean isExits = bitmapService.get(RedisConstant.REDIS_SKU_INFO_BITMAP, skuId);
        if (!isExits) {
            //如果不存在直接抛出异常
            log.info("位图不存在...");
            throw new GmallException(ResultCodeEnum.BITMAP_NOT_EXITS);
        }
        //位图存在的化，可以回源，回源之前先加锁
        RLock lock = redissonClient.getLock(RedisConstant.REDIS_SKU_INOF_LOCK_PREFIX + cacheKey);

        if (lock.tryLock()) {
            //如果抢到锁了直接回源
            log.info("抢锁成功，回源...");
            SkuDetailTO skuDetailTO = getSkuDetailTOWithSource(skuId);
            String jsonSku = JSONs.objecttoString(skuDetailTO);
            //回源以后把数据存到数据库
            cacheService.set(cacheKey, jsonSku, RedisConstant.REDIS_SKU_INFO_TTL);
            // 直接返回
            return skuDetailTO;
        }
        log.info("抢锁成功失败...");
        try {
            TimeUnit.MILLISECONDS.sleep(RedisConstant.REDIS_SKU_SLEEP_TIME);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //睡眠过后直接返回
        return cacheService.get(cacheKey, SkuDetailTO.class);
    }


    public SkuDetailTO getSkuDetailWithCache(Long skuId) {
        //先从缓存拿到数据
        String skuInfoCache = stringRedisTemplate.opsForValue().get(RedisConstant.REDIS_SKU_INFO_KEY + skuId);
        if (!StringUtils.isEmpty(skuInfoCache)) {
            //redis能查到就直接返回
            return JSONs.stringToObj(skuInfoCache, SkuDetailTO.class);
        }
        RLock lock = redissonClient.getLock("lock::" + skuId);
        //缓存没有再看位图有没有该id
        try {
            Boolean bitmap = stringRedisTemplate.opsForValue().getBit("bitmap:skuIds", skuId);
            if (bitmap == null) {
                return null;
            }
            if (!bitmap.booleanValue()) {
                return null;
            }
            while (!lock.tryLock()) {
                Thread.sleep(1000);
                skuInfoCache = stringRedisTemplate.opsForValue().get(RedisConstant.REDIS_SKU_INFO_KEY + skuId);
                if (!StringUtils.isEmpty(skuInfoCache)) {
                    //redis能查到就直接返回
                    return JSONs.stringToObj(skuInfoCache, SkuDetailTO.class);
                }
            }
            SkuDetailTO skuDetailTO = getSkuDetailTOWithSource(skuId);
            //并把数据存到redis里面
            stringRedisTemplate.opsForValue().set(RedisConstant.REDIS_SKU_INFO_KEY + skuId,
                    JSONs.objecttoString(skuDetailTO));
            return skuDetailTO;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public SkuDetailTO getSkuDetailForRedis(Long skuId) {
        //先从缓存拿到数据
        String skuInfoCache = stringRedisTemplate.opsForValue().get(RedisConstant.REDIS_SKU_INFO_KEY + skuId);
        if (!StringUtils.isEmpty(skuInfoCache)) {
            //redis能查到就直接返回
            return JSONs.stringToObj(skuInfoCache, SkuDetailTO.class);
        }
        //缓存没有就回源
        SkuDetailTO skuDetailTO = getSkuDetailTOWithSource(skuId);
        //并把数据存到redis里面
        stringRedisTemplate.opsForValue().set(RedisConstant.REDIS_SKU_INFO_KEY + skuId,
                JSONs.objecttoString(skuDetailTO));
        return skuDetailTO;
    }

    private SkuDetailTO getSkuDetailTOWithSource(Long skuId) {
        //封装返回值
        SkuDetailTO skuDetailTO = new SkuDetailTO();
        SkuDetailTO.SkuInfoVo skuInfoVo = new SkuDetailTO.SkuInfoVo();

        //创建线程,执行有返回值的异步任务，因为后面有些线程需要用到该线程执行的结果
        CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //远程调用拿到SkuInfo
            SkuInfo skuInfo = skuInfoFeginClient.getSkuInfo(skuId).getData();
            skuInfoVo.setId(skuInfo.getId());
            skuInfoVo.setSkuName(skuInfo.getSkuName());
            skuInfoVo.setSpuId(skuInfo.getSpuId());
            skuInfoVo.setSkuDefaultImg(skuInfo.getSkuDefaultImg());
            skuInfoVo.setWeight(skuInfo.getWeight());
            return skuInfo;
        }, threadPoolExecutor);

        CompletableFuture<Void> skuImageCompletableFuture = CompletableFuture.runAsync(() -> {
            //查出该sku下的图片并转换为TO对象
            List<SkuDetailTO.SkuImageVo> skuImageList = skuInfoFeginClient.getSkuImgs(skuId).getData().stream()
                    .map(item -> {
                        SkuDetailTO.SkuImageVo skuImageVo = new SkuDetailTO.SkuImageVo();
                        BeanUtils.copyProperties(item, skuImageVo);
                        return skuImageVo;
                    }).collect(Collectors.toList());
            //将图片信息封装到skuInfo
            skuInfoVo.setSkuImageList(skuImageList);
            skuDetailTO.setSkuInfo(skuInfoVo);
        }, threadPoolExecutor);


        //能够拿到上次执行的结果，接下来运行一个异步任务，在指定线程池运行
        CompletableFuture<Void> categoryViewCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            //根据skuInfo里面的三级id拿到当前sku的分类信息
            CategoryView categoryView = categoryFeignClient.getCategoryByC3Id(skuInfo.getCategory3Id()).getData();
            skuDetailTO.setCategoryView(categoryView);
        }, threadPoolExecutor);

        //不用等待其他线程的结果也不用返回，直接开启线程执行
        CompletableFuture<Void> priceCompletableFuture = CompletableFuture.runAsync(() -> {
            //查询价格
            BigDecimal price = skuInfoFeginClient.getPrice(skuId).getData();
            skuDetailTO.setPrice(price);
        }, threadPoolExecutor);

        //需要等待skuInfo执行结束，获取spuId才能执行
        CompletableFuture<Void> saleAttrCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            //查询销售属性
            List<SpuSaleAttr> saleAttrList = skuInfoFeginClient.getSpuSaleAttrListBySpuIdAndSkuId(skuId,
                    skuInfo.getSpuId()).getData();
            skuDetailTO.setSpuSaleAttrList(saleAttrList);
        }, threadPoolExecutor);

        //需要等待skuInfo执行结束，获取spuId才能执行
        CompletableFuture<Void> spuValueCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            //查询该spu下的所有组合
            String spuValue = skuInfoFeginClient.getValuesSkuJsonTO(skuInfo.getSpuId()).getData();
            skuDetailTO.setValuesSkuJson(spuValue);
        }, threadPoolExecutor);


        //这些线程全部执行完，join()在主线程之前避免没执行完就返回
        CompletableFuture.allOf(spuValueCompletableFuture,
                saleAttrCompletableFuture,
                priceCompletableFuture,
                categoryViewCompletableFuture,
                skuImageCompletableFuture).join();
        return skuDetailTO;
    }
}
